/**
 * Copyright (c) 2011 Tomi Paananen.
 *
 * Part of Quick Gestures application.
 */

#include "gesturevisualizer.h"

// Qt includes
#include <QPainter>
#include <QPen>

// qgui includes
#include "qguiconstants.h"

// qgengine includes
#include <gesture.h>
#include <qgtrace.h>


/*!
  \class GestureVisualizer
  \brief A declarative item for displaying a gesture. This object can be used
         within QML code.
*/


/*!
  Constructor.
*/
GestureVisualizer::GestureVisualizer(QDeclarativeItem *parent /* = 0 */)
    : QDeclarativeItem(parent),
      m_gesture(0),
      m_animationIndex(0),
      m_animationState(Stopped),
      m_scaleRatio(1),
      m_autoScale(true),
      m_contentWidth(0),
      m_contentHeight(0),
      m_drawNoGestureIndicator(true),
      m_lineWidth(QGUi::DefaultVisualizerLineWidth),
      m_lineColor(QGUi::DefaultVisualizerLineColor)
{
    setFlag(QGraphicsItem::ItemHasNoContents, false);

    // Setup the animation timer.
    m_animationTimer.setInterval(1000 / QGUi::VisualizerAnimationFramerate);
    connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(animate()));
}


/*!
  Destructor.
*/
GestureVisualizer::~GestureVisualizer()
{
}


/*!
  From QGraphicsItem.
  Paints the gesture.
*/
void GestureVisualizer::paint(QPainter *painter,
                              const QStyleOptionGraphicsItem *option,
                              QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    painter->setRenderHint(QPainter::Antialiasing, true);

    // Store the original composition mode and set a new one.
    /*QPainter::CompositionMode originalCompositionMode =
            painter->compositionMode();
    painter->setCompositionMode(QPainter::CompositionMode_Source);*/

    if (!m_gesture && m_drawNoGestureIndicator) {
        // No gesture set, draw a no-gesture indicator.
        drawNoGestureIndicator(painter);
    }
    else {
        if (m_animationTimer.isActive()) {
            if (m_gesture->attributes() == Gesture::FixedDirection) {
                // Only fixed direction.
                drawGesture(painter);
                drawDirectionIndicator(painter, m_animationIndex);
            }
            else {
                // Fixed starting point and fixed direction.
                drawGesture(painter, m_animationIndex);
            }
        }
        else {
            drawGesture(painter);
        }
    }

    // Restore the original composition mode.
    //painter->setCompositionMode(originalCompositionMode);
}


/*!
  Returns the gesture.
*/
Gesture *GestureVisualizer::gesture() const
{
    return m_gesture;
}


/*!
  Sets the gesture.
*/
void GestureVisualizer::setGesture(Gesture *gesture)
{
    DEBUG_INFO(gesture);

    // Stop the animation if running.
    stop();

    if (m_gesture) {
        // Disconnect old connections.
        disconnect(this, SLOT(pointsChanged()));
    }

    // Set the new gesture.
    m_gesture = gesture;

    if (m_gesture) {
        // Scale the gesture.
        rescale();

        // Start animation if supported by the gesture.
        if (!play()) {
            // Failed to start the animation. Paint the new gesture.
            update();
        }

        connect(gesture, SIGNAL(pointCountChanged(int)),
                this, SLOT(pointsChanged()));
    }
    else {
        // No gesture set.
        m_scaleRatio = 1;
        m_contentWidth = 0;
        m_contentHeight = 0;
        emit contentWidthChanged(0);
        emit contentHeightChanged(0);

        // Repaint the item to clear previous gesture.
        update();
    }

    emit gestureChanged(m_gesture);
}


/*!
  Returns the content width.
*/
int GestureVisualizer::contentWidth() const
{
    return m_contentWidth;
}


/*!
  Returns the content height.
*/
int GestureVisualizer::contentHeight() const
{
    return m_contentHeight;
}


/*!
  Returns true if automatic scaling is enabled, false otherwise.
*/
bool GestureVisualizer::autoScale() const
{
    return m_autoScale;
}


/*!
  Enables/disable automatic scaling depending on \a enable.
*/
void GestureVisualizer::setAutoScale(bool enable)
{
    m_autoScale = enable;
}


/*!
  Returns the line width.
*/
int GestureVisualizer::lineWidth() const
{
    return m_lineWidth;
}


/*!
  Sets the line width to \a width.
*/
void GestureVisualizer::setLineWidth(int width)
{
    if (width > 0) {
        m_lineWidth = width;
    }
}


/*!
  Returns the line color.
*/
QColor GestureVisualizer::lineColor() const
{
    return m_lineColor;
}


/*!
  Sets the line color as \a color.
*/
void GestureVisualizer::setLineColor(QColor color)
{
    m_lineColor = color;
}


/*!
  Returns true if the animations are playing.
*/
bool GestureVisualizer::playing() const
{
    return (m_animationState == Playing);
}


/*!
  If \a playing is ture, will start playing the animations. Otherwise the
  animations will be stopped.
*/
void GestureVisualizer::setPlaying(bool playing)
{
    if (playing) {
        play();
    }
    else {
        if (m_animationState == Playing) {
            pause();
        }
        else {
            stop();
        }
    }
}


/*!
  Rescales the dimensions.
*/
void GestureVisualizer::rescale(QRectF rect /* = QRect() */)
{
    if (!m_gesture) {
        return;
    }

    if (rect.isNull()) {
        rect = boundingRect();
    }

    // Re-extract the properties.
    m_dataHelper.setGesture(*m_gesture);

    // Calculate the scale ratio and the size of the content.
    int margin(m_lineWidth * 3);
    qreal scaleRatioByWidth = (rect.width() - margin) / m_dataHelper.greaterSideLength();
    qreal scaleRatioByHeight = (rect.height() - margin) / m_dataHelper.greaterSideLength();

    m_scaleRatio = scaleRatioByWidth < scaleRatioByHeight ?
                scaleRatioByWidth : scaleRatioByHeight;

    m_contentWidth = (m_dataHelper.maxX() - m_dataHelper.minX() + margin) * m_scaleRatio;
    m_contentHeight = (m_dataHelper.maxY() - m_dataHelper.minY() + margin) * m_scaleRatio;
    emit contentWidthChanged(m_contentWidth);
    emit contentHeightChanged(m_contentHeight);
}


/*!
  Sets the gesture as a pointer to QObject type.
*/
void GestureVisualizer::setGestureAsQObject(QObject *gesture)
{
    setGesture(qobject_cast<Gesture*>(gesture));
}


/*!
  Starts playing the animation if supported by the gesture attributes.
  Returns true if started playing, false otherwise e.g. in a case where the
  animation is not supported by the gesture (attributes).
*/
bool GestureVisualizer::play()
{
    if (m_gesture && m_gesture->attributes() & Gesture::FixedDirection
            && (m_animationState == Stopped || m_animationState == Paused))
    {
        m_animationTimer.start();
        m_animationState = Playing;
        emit playingChanged(true);
        return true;
    }

    return false;
}


/*!
  Pauses the animations.
*/
void GestureVisualizer::pause()
{
    if (m_animationState == Playing) {
        m_animationTimer.stop();
        m_animationState = Paused;
        emit playingChanged(false);
    }
}


/*!
  Stops the animations.
*/
void GestureVisualizer::stop()
{
    if (m_animationState == Playing || m_animationState == Paused) {
        m_animationTimer.stop();
        m_animationIndex = 0;
        update();
        m_animationState = Stopped;
        emit playingChanged(false);
    }
}


/*!
  Clears the gesture points.
*/
void GestureVisualizer::clear()
{
    m_gesture->clear();
}


/*!
  From QDeclarativeItem.
  Recalculates the scale ratio and the size of the content.
*/
void GestureVisualizer::geometryChanged(const QRectF &newGeometry,
                                        const QRectF &oldGeometry)
{
    // Ignore x and y changes.
    if (newGeometry.width() != oldGeometry.width() ||
            newGeometry.height() != oldGeometry.height()) {
        rescale(newGeometry);
    }
}


/*!
  Draws an image indicating that no gesture is set.
*/
void GestureVisualizer::drawNoGestureIndicator(QPainter *painter) const
{
    QPen pen = QPen(Qt::red);
    pen.setStyle(Qt::DashLine);
    pen.setWidth(m_lineWidth);
    painter->setPen(pen);

    QRectF rect(boundingRect());

    painter->drawRect(rect);
    painter->drawLine(rect.topLeft(), rect.bottomRight());
    painter->drawLine(rect.topRight(), rect.bottomLeft());
}


/*!
  Draws the gesture.
*/
void GestureVisualizer::drawGesture(QPainter *painter,
                                    int lastPointIndexToDraw /* = -1 */)
{
    if (!m_gesture) {
        return;
    }

    PointList points = m_gesture->points();
    const int pointCount = points.count();

    if (pointCount < 2) {
        // No lines to draw!
        return;
    }

    QPen pen = QPen(m_lineColor);
    pen.setStyle(Qt::SolidLine);
    pen.setWidth(m_lineWidth);
    painter->setPen(pen);

    QPoint point1;
    QPoint point2;

    int lastIndex = lastPointIndexToDraw < 0 ? pointCount - 1: lastPointIndexToDraw;

    // Fail safe
    if (lastIndex >= pointCount) {
        lastIndex = pointCount - 1;
    }

    for (int i = 1; i < lastIndex; ++i) {
        point1 = points.at(i - 1);
        point2 = points.at(i);

        if (point1 == QGEngine::BreakPoint || point2 == QGEngine::BreakPoint) {
            continue;
        }

        if (m_autoScale) {
            // Scale the points.
            scalePoint(point1);
            scalePoint(point2);
        }

        painter->drawLine(point1, point2);
    }
}


/*!
  Draws an indicator indicating the direction of the gesture.
*/
void GestureVisualizer::drawDirectionIndicator(QPainter *painter,
                                               int currentIndex)
{
    if (!m_gesture) {
        return;
    }

    // Resolve the point.
    QPoint point(QGEngine::BreakPoint);
    int index(currentIndex - 1);

    // Make sure we don't draw an indicator to a position of a break point.
    while (point == QGEngine::BreakPoint) {
        ++index;

        if (index >= m_gesture->points().count()) {
            return;
        }

        point = m_gesture->points().at(index);
    }

    if (m_autoScale) {
        scalePoint(point);
    }

    QPen pen = QPen(m_lineColor);
    pen.setStyle(Qt::SolidLine);
    pen.setWidth(m_lineWidth);
    painter->setPen(pen);

    // Draw a dot.
    painter->drawEllipse(point, m_lineWidth, m_lineWidth);
}


/*!
  Scales the coordinates of \a point according to the size of this item.
*/
void GestureVisualizer::scalePoint(QPoint &point)
{
    const int halfMargin(m_lineWidth * 1.5);
    point.setX((point.x() - m_dataHelper.minX()) * m_scaleRatio + halfMargin);
    point.setY((point.y() - m_dataHelper.minY()) * m_scaleRatio + halfMargin);
}


/*!
  Runs the animation.
*/
void GestureVisualizer::animate()
{
    ++m_animationIndex;

    if (!m_gesture || m_animationIndex >= m_gesture->points().count()) {
        if (m_gesture->attributes() != Gesture::FixedDirection) {
            // Wait for awhile until starting over.
            m_animationTimer.stop();
            QTimer::singleShot(3000, this,
                               SLOT(startAnimationsFromTheBeginning()));
        }

        m_animationIndex = 0;
    }

    update();
}


/*!
  Starts the animations from the beginning, usually after a pause. This slot is
  for internal use only.
*/
void GestureVisualizer::startAnimationsFromTheBeginning()
{
    if (m_animationState == Playing) {
        m_animationTimer.start();
    }
}


/*!
  Repaints the gesture.
*/
void GestureVisualizer::pointsChanged()
{
    update();
}


QML_DECLARE_TYPE(GestureVisualizer)

// End of file.
